package org.hrqing.chess.util;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hrqing.chess.entity.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Date;
import java.sql.ResultSet;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author Hrqing
 */
public class ClassUtils {

    public static <T> T resultSet(ResultSet resultSet, Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Constructor<T> constructor = clazz.getConstructor();
        T instance = constructor.newInstance();
        List<Field> fields = fields(clazz);
        for (Field field : fields) {
            try {
                Method method = method(field);
                Object invoke = method.invoke(resultSet, field.getName());
                if (invoke != null) {
                    Object value = format(field, invoke);
                    field.setAccessible(true);
                    field.set(instance, value);
                    field.setAccessible(false);
                }
            }catch (Exception ignored){
            }
        }
        return instance;
    }

    private static Object format(Field field, Object value) {
        if (field.getType().isEnum()) {
            try {
                return Enum.valueOf((Class<Enum>)field.getType(), value.toString());
            }catch (Exception ignored) {
            }
        }else if (field.getType().equals(java.util.Date.class) || field.getType().equals(java.sql.Date.class)
                || field.getType().equals(java.sql.Timestamp.class)
                || field.getType().equals(LocalDate.class) || field.getType().equals(LocalDateTime.class)) {
            long date = Long.parseLong(value.toString());
            if (field.getType().equals(java.util.Date.class)) {
                return new java.util.Date(date);
            }else if (field.getType().equals(java.sql.Date.class)) {
                return new java.sql.Date(date);
            }else if (field.getType().equals(java.sql.Timestamp.class)) {
                return new java.sql.Timestamp(date);
            }else if (field.getType().equals(LocalDate.class)) {
                return Instant.ofEpochMilli(date).atZone(ZoneId.systemDefault()).toLocalDate();
            }else if (field.getType().equals(LocalDateTime.class)) {
                return Instant.ofEpochMilli(date).atZone(ZoneId.systemDefault()).toLocalDateTime();
            }
        }
        return value;
    }

    private static Method method(Field field) throws NoSuchMethodException {
        Class<?> type = field.getType();
        if (type.equals(String.class) || type.isEnum()) {
            return ResultSet.class.getMethod("getString", String.class);
        }else if (type.equals(long.class) || type.equals(Long.class)
                || type.equals(java.util.Date.class) || type.equals(java.sql.Date.class) || type.equals(java.sql.Timestamp.class)
                || type.equals(LocalDate.class) || type.equals(LocalDateTime.class)) {
            return ResultSet.class.getMethod("getLong", String.class);
        } else if (type.equals(int.class) || type.equals(Integer.class)) {
            return ResultSet.class.getMethod("getInt", String.class);
        }
        throw new NoSuchMethodException();
    }

    private static List<Field> fields(Class<?> clazz) {
        List<Class<?>> classes = supperClass(clazz);
        List<Field> fields = new ArrayList<>(0);
        classes.forEach(each -> fields.addAll(Arrays.asList(each.getDeclaredFields())));
        return fields;
    }

    private static List<Class<?>> supperClass(Class<?> clazz) {
        if (clazz == null) {
            return new ArrayList<>(0);
        }
        List<Class<?>> results = new ArrayList<>(0);
        results.add(clazz);

        Class<?> superclass = clazz.getSuperclass();
        if (superclass != null && !superclass.equals(Object.class)) {
            List<Class<?>> classes = supperClass(superclass);
            results.addAll(classes);
        }
        return results;
    }

}
